package com.osi.project.common.intercaptor;

import com.osi.start.common.util.LogUtil;
import com.osi.start.common.util.SpringContextHolder;
import com.osi.start.framework.web.autoconfigure.WebProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.util.CollectionUtils;

import java.text.DateFormat;
import java.util.*;
import java.util.regex.Matcher;

/**
 * mybatis sql日志打印
 *
 * @author leo xu
 * @date 2019年3月22日下午11:31:29
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "queryCursor", args = {MappedStatement.class, Object.class,
                RowBounds.class})})
@Slf4j
public class SqlLogInterceptor implements Interceptor {

    private static final String QUOTE = "\\?";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object returnValue = null;
        long start = System.currentTimeMillis();
        try {
            returnValue = invocation.proceed();
            return returnValue;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw e;
        } finally {
            WebProperties webProperties = SpringContextHolder.getBean("webProperties", WebProperties.class);

            if (webProperties != null && webProperties.getSqlLogInterceptor()) {
                long end = System.currentTimeMillis();
                long time = (end - start);
                MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
                BoundSql boundSql = null;
                if (invocation.getArgs().length == 6) {
                    boundSql = (BoundSql) invocation.getArgs()[5];
                } else {
                    Object parameter = invocation.getArgs()[1];
                    boundSql = mappedStatement.getBoundSql(parameter);
                }
                String sqlId = mappedStatement.getId();
                Configuration configuration = mappedStatement.getConfiguration();
                showSql(configuration, boundSql, sqlId, time, returnValue);
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * sql日志拼接
     *
     * <p>
     * 不能用换行。如果使用换行，在logstash中日志的顺序将会混乱
     *
     * @param configuration
     * @param boundSql
     * @param sqlId
     * @param time
     * @param returnValue
     */
    public static void showSql(Configuration configuration, BoundSql boundSql, String sqlId, long time,
                               Object returnValue) {
        String separator = " ==> ";
        String sql = getSql(configuration, boundSql);
        StringBuilder str = new StringBuilder((sql.length() > 256) ? 256 : 64);
        str.append(sqlId);
        str.append("：");
        str.append(sql);
        str.append(separator);
        str.append("spend：");
        str.append(time);
        str.append("ms");
        str.append(separator);
        str.append("result===>");
        str.append(returnValue);

        LogUtil.info(str.toString(), 10000);
    }

    private static String getParameterValue(Object obj) {
        String params = "";
        if (obj instanceof String) {
            params = "'" + String.valueOf(obj) + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            params = "'" + formatter.format(obj) + "'";
        } else if (Objects.isNull(obj)) {
            params = "null";
        } else {
            params = obj.toString();
        }

        return Matcher.quoteReplacement(params);
    }

    public static String getSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (CollectionUtils.isEmpty(parameterMappings) || Objects.isNull(parameterObject)) {
            return sql;
        }

        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            sql = sql.replaceFirst(QUOTE, getParameterValue(parameterObject));
        } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping parameterMapping : parameterMappings) {
                String propertyName = parameterMapping.getProperty();
                if (metaObject.hasGetter(propertyName)) {
                    Object obj = metaObject.getValue(propertyName);
                    sql = sql.replaceFirst(QUOTE, getParameterValue(obj));
                } else if (boundSql.hasAdditionalParameter(propertyName)) {
                    Object obj = boundSql.getAdditionalParameter(propertyName);
                    sql = sql.replaceFirst(QUOTE, getParameterValue(obj));
                }
            }
        }

        return sql;
    }

}